<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>烟花</title>
  <style>
    body {
      margin: 0;
      padding: 0;
      overflow: hidden;
    }
  </style>
</head>

<body>
  <canvas id='canvas'></canvas>
</body>
<script>
  const random = function (min, max) {
    return Math.random() * (max - min) + min
  }
  const randomColor = function () {
    let colors = [
      "#f44336",
      "#e91e63",
      "#9c27b0",
      "#673ab7",
      "#3f51b5",
      "#2196f3",
      "#03a9f4",
      "#00bcd4",
      "#009688",
      "#4CAF50",
      "#8BC34A",
      "#CDDC39",
      "#FFEB3B",
      "#FFC107",
      "#FF9800",
      "#FF5722",
    ];

    let num = Math.floor(Math.random() * colors.length)
    return colors[num]
  };
  const canvas = document.querySelector("#canvas")
  const ctx = canvas.getContext("2d")
  const _W = canvas.width = window.innerWidth
  const _H = canvas.height = window.innerHeight

  class FireWork {
    constructor(x, radius, color, boomPoint) {
      this.x = x
      this.y = _W
      this.radius = radius
      this.color = color
      this.booms = []
      this.boomPoint = boomPoint
      this.boomArea = random(60, 150)
      this.dead = false
      this.sparkDead = false
    }

    move() {
      // 与爆炸位置的距离,逐渐变小
      let dx = this.boomPoint.x - this.x
      let dy = this.boomPoint.y - this.y
      // 跟随距离改变上升速度,逐渐变慢
      this.x = this.x + dx * 0.01
      this.y = this.y + dy * 0.01
      // 后期烟花移动速度过慢,故给一个距离范围,进入范围便爆炸
      if (Math.abs(dx) <= this.boomArea && Math.abs(dy) <= this.boomArea) {
        this.dead = true
        this.initBoom()
      }
    }

    draw() {
      ctx.save()
      ctx.beginPath()
      ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI)
      ctx.fillStyle = this.color
      ctx.fill()
      ctx.restore()
    }

    drawTail() {
      ctx.save()
      ctx.fillStyle = "rgba(186,186,86,0.3)"
      ctx.beginPath()
      ctx.arc(this.x, this.y, random(this.radius, this.radius + 4), 0, 2 * Math.PI)
      ctx.fill()
      ctx.restore()
    }

    initBoom() {
      let num = random(50, 200)
      for (let i = 0; i < num; i++) {
        let tan = random(-Math.PI, Math.PI);
        let x = random(0, 250) * Math.cos(tan) + this.x;
        let y = random(0, 250) * Math.sin(tan) + this.y;
        let boom = new Spark(this.x, this.y, random(0, 1), randomColor(), x, y);
        this.booms.push(boom);
      }
    }

    boomAction() {
      let deadNum = 1
      for (let i = 0; i < this.booms.length; i++) {
        if (!this.booms[i].dead) {
          this.booms[i].update();
        } else {
          deadNum++
        }
      }
      if (deadNum == this.booms.length) this.sparkDead = true
    }

    update() {
      if (!this.dead) {
        this.move()
        this.draw()
        this.drawTail()
      } else {
        this.boomAction()
      }
    }
  }

  class Spark {
    constructor(centerX, centerY, radius, color, dieX, dieY) {
      this.dieX = dieX;
      this.dieY = dieY;
      this.x = centerX;
      this.y = centerY;
      this.dead = false;
      this.centerX = centerX;
      this.centerY = centerY;
      // 火花大小差异化
      this.radius = random(radius, radius + 1);
      this.color = color;
    }
    draw() {
      ctx.save();
      ctx.beginPath();
      ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
      ctx.fillStyle = this.color;
      ctx.fill()
      ctx.restore();
    }
    move() {
      // 模拟重力下降
      this.dieY = this.dieY + this.radius / 10
      let dx = this.dieX - this.x
      let dy = this.dieY - this.y
      // 如果距离小于 0.1 则坐标直接等于消失坐标
      this.x = Math.abs(dx) < 0.1 ? this.dieX : (this.x + dx * 0.1);
      this.y = Math.abs(dy) < 0.1 ? this.dieY : (this.y + dy * 0.1);
      // x 达到消失坐标,则消失
      if (dx == 0 && Math.abs(dy) <= 80) {
        this.dead = true;
      }
    }
    update() {
      if (!this.dead) {
        this.move()
        this.draw()
      }
    }
  }

  const particles = []
  let oldTime = new Date()

  const animation = function () {
    window.requestAnimationFrame(animation);
    // 使用 透明度0.1填充画布,实现烟花拖拽特效
    ctx.fillStyle = "rgba(0, 0, 0, 0.1)";
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    // 间隔时间生成烟花
    let newDate = new Date()
    if (newDate - oldTime > random(300, 1500)) {
      let o = new FireWork(random(_W / 4, 0.8 * _W), 3, "#FFF", {
        x: random(_W / 4, 0.8 * _W),
        y: random(50, 200)
      })
      particles.push(o)
      oldTime = newDate
    }
    // 遍历烟花和删除死亡烟花
    particles.forEach((element, index) => {
      element.update()
      if (element.sparkDead) {
        particles.splice(index, 1);
      }
    });
  };

  animation()
</script>

</html>